package com.sojson.util.txy;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.sojson.config.exception.TransErrorCode;
import com.sojson.config.exception.bean.Status;
import com.sojson.constant.Constant;
import com.sojson.util.ExpUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.SystemParamBaseUtil;
import com.sojson.util.cache.CacheUtil;
import com.sojson.util.encrypt.oneway.alike.sha1.Sha1Util;
import com.sojson.util.httpclient.HttpClientUtil;
import com.sojson.util.id.IdUtil;
import com.sojson.util.json.JsonUtil;
import com.sojson.util.properties.PropertiesUtil;
import com.sojson.util.text.Convert;
import com.sojson.util.token.TokenUtil;
import com.sojson.util.txy.bean.TxyParam;
import com.sojson.util.txy.bean.TxyResponse;
import com.sojson.util.txy.bean.TxyResult;
import com.sojson.util.txy.bean.child.TxyResultResponse;
import com.sojson.util.txy.bean.child.TxyTicketsResponse;
import com.sojson.util.txy.constant.TxyConstant;
import com.sojson.util.txy.enums.ETxyApiType;
import com.sojson.util.txy.enums.ETxyJsapiTicketType;
import com.sojson.util.txy.enums.ETxyLiveInterType;
import com.sojson.util.txy.enums.ETxyResultType;

/**
 * 腾讯云工具类
 * 
 * @author liu
 * @date 2020-11-02
 */
public class TxyUtil {

    /** AccessToken在缓存中的前缀,不设置为TxyAccessToken: */
    private static final String ACCESS_TOKEN_CACHE_PREFIX =
        Convert.toStr(Constant.PROPERTIES.getProperty("so.cache.txy.accessToken.name.prefix"), "TxyAccessToken:");
    /** AccessToken在缓存中的超时时间(以秒为单位),不设置为两小时 */
    private static final int ACCESS_TOKEN_CACHE_TIMEOUT = Convert.toInt(
        PropertiesUtil.getNumberFormat(Constant.PROPERTIES.getProperty("so.cache.txy.accessToken.timeout")),
        60 * 60 * 2);
    /** JsapiTicket在缓存中的前缀 */
    private static final String JSAPI_TICKET_CACHE_PREFIX =
        Constant.PROPERTIES.getProperty("so.cache.txy.jsapiTicket.name.prefix");
    /** JsapiTicket在缓存中的超时时间(以秒为单位),不设置为两小时 */
    private static final int JSAPI_TICKET_CACHE_TIMEOUT = Convert.toInt(
        PropertiesUtil.getNumberFormat(Constant.PROPERTIES.getProperty("so.cache.txy.jsapiTicket.timeout")),
        60 * 60 * 2);

    /**
     * 获取AccessToken
     * 
     * @return
     * @throws Exception
     */
    private static String getAccessToken() throws Exception {
        return getAccessToken(getAppId(), getAppsecret());
    }

    /**
     * 获取AccessToken
     * 
     * @param appid
     * @param appsecret
     * @return
     * @throws Exception
     */
    private static String getAccessToken(String appid, String appsecret) throws Exception {
        String accessTokenCacheKey = getAccessTokenCacheKey(appid);
        // 先从缓存中获取
        String accessToken = get(accessTokenCacheKey);
        if (isNotBlankObject(accessToken)) {
            return accessToken;
        }

        // 从腾讯云获取
        String doGet = doGet(String.format(ETxyApiType.ACCESS_TOKEN_URL.getType(), appid, appsecret));
        if (doGet == null) {
            ExpUtil.throwEx("获取腾讯云AccessToken失败: 接口未返回任何信息");
        }

        TxyResponse jsonToObject = jsonToObject(doGet);
        if (jsonToObject.getCusStatus().getCode() != TransErrorCode.SUCCESS.getCode()) {
            ExpUtil.throwEx("获取腾讯云AccessToken失败: " + jsonToObject.getMsg());
        }

        accessToken = jsonToObject.getAccessToken();
        // 获取成功就保存到缓存
        setex(accessTokenCacheKey, accessToken, ACCESS_TOKEN_CACHE_TIMEOUT);
        return accessToken;
    }

    /**
     * 获取基础AccessToken在缓存中的Key
     * 
     * @param appid
     * @return
     */
    private static String getAccessTokenCacheKey(String appid) {
        return ACCESS_TOKEN_CACHE_PREFIX + appid;
    }

    /**
     * 获取腾讯云Sign调用JS接口的临时票据
     * 
     * @return
     * @throws Exception
     */
    private static String getSignJsapiTicket() throws Exception {
        return getSignJsapiTicket(getAppId());
    }

    /**
     * 获取腾讯云Sign调用JS接口的临时票据
     * 
     * @param appid
     * @return
     * @throws Exception
     */
    private static String getSignJsapiTicket(String appid) throws Exception {
        String ticketCacheKey = getTicketCacheKey(appid);

        String string = get(ticketCacheKey);
        if (isNotBlankObject(string)) {
            return string;
        }

        String value = getJsapiTicket(String.format(ETxyApiType.JSAPI_TICKET_SIGN_URL.getType(), appid,
            getAccessToken(), ETxyJsapiTicketType.SIGN));

        // 获取成功就保存到缓存
        setex(ticketCacheKey, value, JSAPI_TICKET_CACHE_TIMEOUT);
        return value;
    }

    /**
     * 获取腾讯云Nonce调用JS接口的临时票据
     * 
     * @param appid
     * @return
     * @throws Exception
     */
    private static String getNonceJsapiTicket(String appid) throws Exception {
        String value = getJsapiTicket(String.format(ETxyApiType.JSAPI_TICKET_NONCE_URL.getType(), appid,
            getAccessToken(), ETxyJsapiTicketType.NONCE, getUserId()));
        return value;
    }

    /**
     * 获取腾讯云调用JS接口的临时票据
     * 
     * @param url
     * @return
     * @throws Exception
     */
    private static String getJsapiTicket(String url) throws Exception {
        String doGet = doGet(url);
        if (doGet == null) {
            ExpUtil.throwEx("获取腾讯云JsapiTicket失败: 接口未返回任何信息");
        }
        TxyResponse jsonToObject = jsonToObject(doGet);
        if (jsonToObject.getCusStatus().getCode() != TransErrorCode.SUCCESS.getCode()) {
            ExpUtil.throwEx("获取腾讯云JsapiTicket失败: " + jsonToObject.getMsg());
        }

        List<TxyTicketsResponse> tickets = jsonToObject.getTickets();
        if (tickets == null || tickets.size() < 1) {
            ExpUtil.throwEx("获取腾讯云JsapiTicket失败: 返回集合为空");
        }

        TxyTicketsResponse ticket = tickets.get(0);
        if (ticket == null) {
            ExpUtil.throwEx("获取腾讯云JsapiTicket失败: 返回ticket为空");
        }

        String value = ticket.getValue();
        if (StringUtil.isBlankString(value)) {
            ExpUtil.throwEx("获取腾讯云JsapiTicket失败: 返回ticket值为空");
        }
        return value;
    }

    /**
     * 获取JsapiTicket在缓存中的Key
     * 
     * @param appid
     * @return
     */
    private static String getTicketCacheKey(String appid) {
        return JSAPI_TICKET_CACHE_PREFIX + appid;
    }

    /**
     * 获取腾讯云签名TxyResult对象
     * 
     * @return
     * @throws Exception
     */
    private static TxyResult getSignatureBean() throws Exception {
        TxyResult txyResult = new TxyResult();
        txyResult.setAppId(getAppId());
        txyResult.setUserId(getUserId());
        txyResult.setNonce(getNonceStr());
        txyResult.setVersion(getVersion());
        txyResult.setTicket(getSignJsapiTicket());
        return txyResult;
    }

    /**
     * 获取腾讯云签名
     * 
     * @param txyResult 腾讯云签名TxyResult对象,通过TxyUtil.getSignatureBean获取
     * @return
     * @throws Exception 
     */
    private static String getSignature(TxyResult txyResult) throws Exception {
        List<String> params = new ArrayList<>();
        params.add(txyResult.getAppId());
        params.add(txyResult.getWebankAppId());
        params.add(txyResult.getUserId());
        params.add(txyResult.getOrderNo());
        params.add(txyResult.getFaceId());
        params.add(txyResult.getNonce());
        params.add(txyResult.getVersion());
        params.add(txyResult.getTicket());

        // 删除空值
        params.removeAll(Collections.singleton(null));
        Collections.sort(params);
        StringBuilder sb = new StringBuilder();
        for (String s : params) {
            sb.append(s);
        }
        return Sha1Util.encrypt(sb.toString());
    }

    /**
     * 获取腾讯云签名
     * 
     * @return
     * @throws Exception 
     */
    @SuppressWarnings("unused")
    private static String getSignature() throws Exception {
        return getSignature(getSignatureBean());
    }

    /**
     * 合作方后台上送身份信息Bean
     * 
     * @param orderNo   订单号
     * @param name      用户姓名
     * @param idNo      用户证件号
     * @return
     * @throws Exception
     */
    private static TxyParam getAdvFaceIdBean(String orderNo, String name, String idNo) throws Exception {
        TxyParam param = new TxyParam();
        param.setAppId(getAppId());
        param.setOrderNo(orderNo);
        param.setName(name);
        param.setIdNo(idNo);
        param.setUserId(getUserId());
        param.setLiveInterType(ETxyLiveInterType.LIVE_INTER.getCode());
        param.setVersion(getVersion());
        param.setNonce(getNonceStr());

        TxyResult result = new TxyResult();
        result.setAppId(param.getAppId());
        result.setUserId(param.getUserId());
        result.setVersion(param.getVersion());
        result.setTicket(getSignJsapiTicket(result.getAppId()));
        result.setNonce(param.getNonce());
        param.setSign(getSignature(result));
        return param;
    }

    /**
     * 合作方后台上送身份信息
     * 
     * @param orderNo   订单号
     * @param name      用户姓名
     * @param idNo      用户证件号
     * @return
     * @throws Exception 
     */
    private static String getAdvFaceId(String orderNo, String name, String idNo) throws Exception {
        TxyParam param = getAdvFaceIdBean(orderNo, name, idNo);

        String doPost =
            doPostByParam(String.format(ETxyApiType.GET_ADV_FACE_ID.getType(), orderNo), objectToJson(param));
        if (doPost == null) {
            ExpUtil.throwEx("腾讯云合作方后台上送身份信息失败: 接口未返回任何信息");
        }
        TxyResponse jsonToObject = jsonToObject(doPost);
        if (jsonToObject.getCusStatus().getCode() != TransErrorCode.SUCCESS.getCode()) {
            ExpUtil.throwEx("腾讯云合作方后台上送身份信息失败: " + jsonToObject.getMsg());
        }

        TxyResultResponse result = jsonToObject.getResult();
        if (result == null) {
            ExpUtil.throwEx("腾讯云合作方后台上送身份信息失败: 返回响应对象为空");
        }

        String faceId = result.getFaceId();
        if (StringUtil.isBlankString(faceId)) {
            ExpUtil.throwEx("腾讯云合作方后台上送身份信息失败: 返回faceId为空");
        }
        return faceId;
    }

    /**
     * 获取人脸验证参数
     * 
     * @param orderNo   订单号
     * @param name      用户姓名
     * @param idNo      用户证件号
     * @return
     * @throws Exception
     */
    public static TxyResult getFaceVerifyParam(String orderNo, String name, String idNo) throws Exception {
        TxyResult result = new TxyResult();
        result.setWebankAppId(getAppId());
        result.setOrderNo(orderNo);
        result.setUserId(getUserId());
        result.setVersion(getVersion());
        result.setFaceId(getAdvFaceId(result.getOrderNo(), name, idNo));
        result.setJsapiTicket(getNonceJsapiTicket(result.getWebankAppId()));
        result.setNonce(getNonceStr());

        // 获取签名
        result.setSign(getSignature(result));
        result.setJsapiTicket(null);

        result.setUrl(getNotyfiUrl());
        result.setResultType(ETxyResultType.NOTIFY_URL.getCode());
        return result;
    }

    /**
     * 获取人脸验证结果Bean
     * 
     * @param orderNo   订单号
     * @return
     * @throws Exception
     */
    private static TxyParam getFaceVerifyResultBean(String orderNo) throws Exception {
        TxyParam param = new TxyParam();
        param.setAppId(getAppId());
        param.setOrderNo(orderNo);
        param.setVersion(getVersion());
        param.setNonce(getNonceStr());

        TxyResult result = new TxyResult();
        result.setAppId(param.getAppId());
        result.setOrderNo(param.getOrderNo());
        result.setVersion(param.getVersion());
        result.setNonce(param.getNonce());
        result.setTicket(getSignJsapiTicket());
        param.setSign(getSignature(result));
        return param;
    }

    /**
     * 获取人脸验证结果
     * 
     * @param orderNo   订单号
     * @return
     * @throws Exception
     */
    public static boolean getFaceVerifyResult(String orderNo) throws Exception {
        TxyParam param = getFaceVerifyResultBean(orderNo);

        String doPost =
            doPostByParam(String.format(ETxyApiType.GET_FACE_VERIFY_RESULT.getType(), orderNo), objectToJson(param));
        if (doPost == null) {
            ExpUtil.throwEx("获取腾讯云人脸核验结果失败: 接口未返回任何信息");
        }
        TxyResponse jsonToObject = jsonToObject(doPost);
        if (jsonToObject.getCusStatus().getCode() != TransErrorCode.SUCCESS.getCode()) {
            return false;
        }
        return true;
    }

    /**
     * 获取用户ID
     * 
     * @return
     * @throws IOException 
     */
    private static String getUserId() throws IOException {
        return TokenUtil.getUserId();
    }

    /**
     * 获取生成密钥的随机字符串 
     * 
     * @return
     */
    private static String getNonceStr() {
        return generateGuid();
    }

    /**
     * 获取接口版本
     * 
     * @return
     */
    private static String getVersion() {
        return "1.0.0";
    }

    /**
     * 获取回调地址
     * 
     * @return
     * @throws IOException 
     */
    private static String getNotyfiUrl() throws IOException {
        return StringUtil.encode(SystemParamBaseUtil.getStr("sys.domain") + "/test");
    }

    /**
     * 获取Appid
     * 
     * @return
     * @throws IOException
     */
    private static String getAppId() throws IOException {
        return getByMapStr(TxyConstant.APPID_CACHE_NAME);
    }

    /**
     * 获取Appsecret
     * 
     * @return
     * @throws IOException
     */
    private static String getAppsecret() throws IOException {
        return getByMapStr(TxyConstant.APPSECRET_CACHE_NAME);
    }

    /**
     * 判断一个对象是否是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isBlankObject(Object obj) {
        return StringUtil.isBlankObject(obj);
    }

    /**
     * 判断一个对象是否不是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isNotBlankObject(Object obj) {
        return StringUtil.isNotBlankObject(obj);
    }

    /**
     * 生成全局唯一的ID
     * 
     * @return
     */
    private static String generateGuid() {
        return IdUtil.uuid();
    }

    /**
     * 将对象转换成json字符串(值为空的不解析)
     * 
     * @param data 被转换的对象
     * @return 得到的字符串
     */
    private static String objectToJson(Object data) {
        return JsonUtil.objectToJson(data);
    }

    /**
     * 将json字符串转换成对象
     * 
     * @param jsonData json字符串
     * @param beanType 要转换成的对象的类型
     * @return 得到的对象
     */
    private static TxyResponse jsonToObject(String jsonData) {
        TxyResponse jsonToObject = JsonUtil.jsonToObject(jsonData, TxyResponse.class);
        Status status = new Status();
        jsonToObject.setCusStatus(status);

        String code = jsonToObject.getCode();
        if (isBlankObject(code) || Integer.parseInt(code) == 0) {
            status.setCode(TransErrorCode.SUCCESS.getCode());
        } else {
            status.setCode(TransErrorCode.FAIL.getCode());
            status.setMsg(jsonToObject.getMsg());
        }
        return jsonToObject;
    }

    /**
     * 发送get请求
     * 
     * @param url 请求地址
     * @return
     * @throws Exception 
     */
    private static String doGet(String url) throws Exception {
        return HttpClientUtil.doGet(url);
    }

    /**
     * 发送post请求
     * 
     * @param url       请求地址
     * @param params    参数(Json数据格式)
     * @return
     * @throws IOException 
     */
    private static String doPostByParam(String url, String params) throws IOException {
        return HttpClientUtil.doPostByParam(url, params);
    }

    /**
     * 从缓存中获取值
     * 
     * @param <T>
     * @param key
     * @return
     * @throws IOException
     */
    private static <T> T get(String key) throws IOException {
        return CacheUtil.get(key);
    }

    /**
     * 从缓存中获取值
     * 
     * @param key
     * @return
     * @throws IOException
     */
    private static String getByMapStr(String key) throws IOException {
        return SystemParamBaseUtil.getStr(key);
    }

    /**
     * 往缓存中存储值,带过期时间(不设置为永久存储,此方法传负数会报错,为了提升性能去掉了if判断,确定不会有负数就调这个方法)
     * 
     * @param key
     * @param value
     * @param timer 过期时间(以秒为单位)
     * @throws IOException
     */
    private static void setex(String key, Object value, int timer) throws IOException {
        CacheUtil.setex(key, value, timer);
    }

}